Store taskprocessing output files with extension to ease mimtype guessing#61689
Store taskprocessing output files with extension to ease mimtype guessing#61689marcelklehr wants to merge 12 commits into
Conversation
a499488 to
944e422
Compare
There was a problem hiding this comment.
Pull request overview
This PR extends the TaskProcessing public API and implementation to better handle “file-shaped” task outputs by allowing providers (and ExApps uploads) to influence stored output filenames via extensions, with the goal of improving MIME type detection downstream. It also annotates several public TaskProcessing interfaces/data objects with #[Implementable] / #[Consumable] attributes.
Changes:
- Introduces
OCP\TaskProcessing\FileShapedand updates synchronous provider return type annotations to allow returningFileShapedvalues. - Extends output validation (
EShapeType::validateOutputWithFileData) and output encapsulation (Manager::encapsulateOutputFileData) to accept file-shaped outputs. - Appends file extensions when storing uploaded/provider output data in AppData, and adds
Implementable/Consumableattributes to several public APIs.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/public/TaskProcessing/SynchronousProviderOptions.php | Marks the options class as #[Consumable] for public API consumption. |
| lib/public/TaskProcessing/ShapeEnumValue.php | Marks the DTO as #[Consumable]. |
| lib/public/TaskProcessing/ShapeDescriptor.php | Marks the DTO as #[Consumable]. |
| lib/public/TaskProcessing/ITriggerableProvider.php | Marks provider interface as #[Implementable] (but since metadata needs correction). |
| lib/public/TaskProcessing/ITaskType.php | Marks task type interface as #[Implementable]. |
| lib/public/TaskProcessing/ISynchronousProvider.php | Marks interface as #[Implementable] and expands psalm return types to include FileShaped. |
| lib/public/TaskProcessing/ISynchronousOptionsAwareProvider.php | Marks interface as #[Implementable] and expands psalm return types to include FileShaped. |
| lib/public/TaskProcessing/IProvider.php | Marks provider interface as #[Implementable]. |
| lib/public/TaskProcessing/IInternalTaskType.php | Marks internal task type interface as #[Implementable] (minor formatting consistency). |
| lib/public/TaskProcessing/FileShaped.php | Adds new public DTO to carry file-shaped output data + metadata (extension/mime type). |
| lib/public/TaskProcessing/EShapeType.php | Updates output validation to allow FileShaped (but list validation currently has critical logic errors). |
| lib/private/TaskProcessing/Manager.php | Stores file outputs using provided extensions (but list branch currently clears unrelated output keys and needs extension normalization). |
| core/Controller/TaskProcessingApiController.php | Appends extension when storing ExApp uploaded files (needs extension sanitization/normalization). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ype guessing Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
…erty Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Assisted-by: ClaudeCode:claude-opus-4-8 Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
8d2c8e2 to
70e7fea
Compare
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
3c6bd44 to
326b94a
Compare
| @@ -534,7 +535,8 @@ public function setFileContentsExApp(int $taskId): DataResponse { | |||
| if (!$handle) { | |||
| return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); | |||
| } | |||
| $fileId = $this->setFileContentsInternal($handle); | |||
| $ext = pathinfo($file['name'], PATHINFO_EXTENSION); | |||
There was a problem hiding this comment.
| $ext = pathinfo($file['name'], PATHINFO_EXTENSION); | |
| $ext = pathinfo(($file['name'] ?? ''), PATHINFO_EXTENSION); |
| if ($this === EShapeType::Image && !is_string($value)) { | ||
| if ($this === EShapeType::Image && !is_string($value) && !($value instanceof FileShaped && $value->getShapeType() === EShapeType::Image)) { | ||
| throw new ValidationException('Non-image item provided for Image slot'); | ||
| } | ||
| if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) { | ||
| if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item) && !($item instanceof FileShaped && $item->getShapeType() === EShapeType::Image))) > 0)) { |
There was a problem hiding this comment.
maybe it would be nice to exact these checks in functions with separate ifs for different checks so they are easier to parse.
they could even be reused for the array checks then.
something along these lines but feel free to do it your way:
/**
* Check whether a single value is valid file output data for the given shape type.
* Accepts raw string data or a FileShaped wrapper whose shape type matches.
*/
private static function isValidFileData(mixed $value, EShapeType $expected): bool {
if (is_string($value)) {
return true;
}
return $value instanceof FileShaped && $value->getShapeType() === $expected;
}
/**
* Check whether a value is a valid list of file output data items.
*/
private static function isValidFileDataList(mixed $value, EShapeType $scalarType): bool {
if (!is_array($value)) {
return false;
}
foreach ($value as $item) {
if (!self::isValidFileData($item, $scalarType)) {
return false;
}
}
return true;
}and can be used like:
if ($this === EShapeType::Image && !self::isValidFileData($value, EShapeType::Image)) {
throw new ValidationException('Non-image item provided for Image slot');
}| throw new ValidationException('Non-file item provided for File slot'); | ||
| } | ||
| if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) { | ||
| if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item) && !($item instanceof FileShaped && $item->getShapeType() === EShapeType::File))) > 0)) { | ||
| throw new ValidationException('Non-audio list item provided for ListOfFiles slot'); |
There was a problem hiding this comment.
| throw new ValidationException('Non-audio list item provided for ListOfFiles slot'); | |
| throw new ValidationException('Non-file list item provided for ListOfFiles slot'); |
| * @psalm-return array<string, list<numeric|string>|numeric|string> | ||
| * @psalm-return array<string, list<numeric|string|FileShaped>|numeric|string|FileShaped> |
There was a problem hiding this comment.
maybe it can be noted that FileShaped addition in the return type was made in 35.0.0
like in
| /** | ||
| * @since 35.0.0 | ||
| */ | ||
| public static function sanitizeExtension(string $ext): string { |
There was a problem hiding this comment.
the extension provider is within the trust boundary of ex-apps and php apps but it may not hurt to reject .php, .phar and .htaccess extensions
Summary
Currently, we have to guess the mime type entirely from the data which is sometimes hard. With this we allow synchronous providers to hint the file extension and mime type.
Also adds some needed attributes.
TODO
Checklist
3. to review, feature component)stable32)AI (if applicable)